/*
 * hd44780.c
 *
 *  Created on: Dec 29, 2011
 *      Author: Orlando Arias
 *	   License: GPLv3
 *
 *    hd44780-4bit
 *    Copyright (C) 2011 Orlando Arias
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <inttypes.h>
#include <stdio.h>

#include "hd44780.h"
#include "hd_base.h"

void hd_init(void) {
	// init LCD controller.

	HDDDR = (0x0F |
#	ifdef _has_bell
			_bv(BELL) |
#	endif
			_bv(EN) | _bv(RS) | _bv(RW));

	_delay_function(40);
	HDPORT = 0x03;
	_hd_strobe();
	_delay_function(4.1);
	HDPORT = 0x03;
	_hd_strobe();
	_delay_function(0.1);
	HDPORT = 0x03;
	_hd_strobe();
	_delay_function(0.1);
	HDPORT = 0x02;
	_hd_strobe();

	_hd_sendRaw(0x28, 0);
	_hd_sendRaw(BLANK_DISPLAY, 0);
	_hd_sendRaw(CURSOR_INVISIBLE, 0);
	_hd_sendRaw(EM_INCREMENT_OFF, 0);
	_hd_sendRaw(CLEAR, 0);
	_hd_sendRaw(MOVE_HOME, 0);
}

int putchar(int c) {
	if(c == '\f') {				// form feed - clear the screen
		_row = 0;
		_col = 0;
		_hd_sendRaw(CLEAR, 0);

	// horizontal movement
	} else if(c == '\r') {		// carriage return - move cursor to line start
		_col = 0;
		_hd_sendRaw(SET_DDRAM | _hd_map[_row], 0);

	} else if(c == '\b') {		// backspace - move cursor left
		if(_col > 0)
			_col--;
		else
			_col = _cols - 1;
		_hd_sendRaw(SET_DDRAM | (_hd_map[_row] + _col), 0);

	} else if(c == '\t') {		// tab - move cursor right
		_col++;
		if(_col == _cols)
			_col = 0;
		_hd_sendRaw(SET_DDRAM | (_hd_map[_row] + _col), 0);

	// vertical movement
	} else if(c == '\v') {		// vertical tab - move cursor to previous line
		if(_row == 0)
			_row = _rows - 1;
		else
			_row--;
		_hd_sendRaw(SET_DDRAM | (_hd_map[_row] + _col), 0);

	} else if(c == '\n') {		// line feed - move cursor to next line
		_row++;
		if(_row == _rows)
			_row = 0;
		_hd_sendRaw(SET_DDRAM | (_hd_map[_row] + _col), 0);

	// miscellaneous and default
	} else if(c == '\a') {		// system bell - see hd_handler.h for details
#		ifdef _has_bell
			HDPORT |= _bv(BELL);
			_delay_function(BELL_LEN);
			HDPORT &= ~_bv(BELL);
#		endif

	} else {					// character to print
#		ifdef _a00_rom
			if(c == 'g' || c == 'j' || c == 'p' || c == 'q' || c == 'y')
				c |= 0x80;
#		endif
		_hd_sendRaw(c, 1);

		_col++;
		if(_col == _cols) {
			_col = 0;
			_row++;
			if(_row == _rows)
				_row = 0;

			_hd_sendRaw(SET_DDRAM | _hd_map[_row], 0);
		}
	}
	return 0;
}

void _hd_sendRaw(char c, uint8_t _rs) {
	_wait_for_busy_flag();			// wait for LCD to be free
	HDPORT &= ~(0x0f | _bv(RW));	// clear low nibble of port previous to
									// operation and clear R/~W for a write

	if (_rs)						// register select
		HDPORT |= _bv(RS);
	else
		HDPORT &= ~_bv(RS);

	HDPORT |= (c >> 4);				// send high nibble
	_hd_strobe();

	HDPORT &= ~(0x0f);
	HDPORT |= (c & 0x0f);			// send low nibble
	_hd_strobe();
}

void _hd_strobe(void) {
	_delay_function(0.000060);		// t_AS = 60ns
	HDPORT |= _bv(EN);
	_delay_function(0.000450);		// PW_EH = 450ns
	HDPORT &= ~_bv(EN);
	_delay_function(0.000020);		// t_AH = 20ns
}

uint8_t _hd_bf(void) {
	uint8_t bf = 0;
	HDPORT |= _bv(RW);
	HDPORT &= ~_bv(RS);				// read operation, command register
	HDDDR &= 0xf0;
	for(uint8_t i = 0; i < 2; i++) {
		_delay_function(0.000060);	// t_AS = 60ns
		HDPORT |= _bv(EN);
		_delay_function(.000225);	// t_DDR = 160ns data is available
		if(i == 0)
			bf = (HDPIN & _bv(BF));
		_delay_function(0.000225);	// PW_EH = 450 ns
		HDPORT &= ~_bv(EN);
		_delay_function(0.000020);	// t_AH = 20 ns
	}
	HDDDR |= 0x0f;
	return bf;
}
